/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Name Service core implementation file.
 * \author Antti Maula <antti.maula@tkk.fi>
 */

//*****************************************************************************
//*****************************************************************************
#include "nameservicecore.h"
#include "nametable.h"
#include "owndebug.h"
#include <ctype.h>
#include <inttypes.h>
#include <string.h>
#include <assert.h>

//*****************************************************************************
//*****************************************************************************
typedef struct SNameServiceCore
{
  /// Validity time Minimum
  uint32_t name_cache_validity_s_min;
  /// Validity time Maximum
  uint32_t name_cache_validity_s_max;
} TNameServiceCore;
//*****************************************************************************
//*****************************************************************************
static TNameServiceCore nameservicecore;
//*****************************************************************************
//*****************************************************************************

static int isValidName(const char * const name, const int len)
{
  int i,r = 0;
  
  // Loop the name
  for(i=0; i < len; i++) {
    if (name[i] == 0) {
      // We found terminating NULL character
      if (i > 0) { 
        // If it was not the first character => ACCEPT
        r = 1;
      } else {
        // If it was the first character (Empty name) => REJECT
        r = 0;
      }
      break;
      
    } else if (!isprint(name[i])) {
      // Found a character that was not NULL, and was not PRINTABLE => REJECT
      r = 0;
      break;
    }
  }
  
  // Return result
  return r;
}
//*****************************************************************************

static int updateValidity(TNameTableEntry *entry)
{
  int result = 0;
  int isExpired;
  if ((isExpired = nametableIsExpiredEntry(entry))) {
    dPrint(ODTEST,"Entry removed due to expiration!");
    nametableDeleteEntryByEntryPtr(entry);
    result = -1;
  }
  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

void nameservicecoreInit(const int expected_max_names, uint32_t validMin, uint32_t validMax)
{
  dPrint(ODTEST,"Called");
  memset(&nameservicecore, 0x00, sizeof(TNameServiceCore));
  nametableInit(expected_max_names);
  nameservicecore.name_cache_validity_s_min = validMin;
  nameservicecore.name_cache_validity_s_max = validMax;
}
//*****************************************************************************

void nameservicecoreDeinit(void)
{
  nameservicecore.name_cache_validity_s_min = 0;
  nameservicecore.name_cache_validity_s_max = 0;
  nametableDeinit();
}
//*****************************************************************************

int nameservicecoreRequestIdByName(TAPNameServiceFrame *replyframe, 
                                   const TAPNameServiceFrame *nsframe, 
                                   const TFastProtocolHeader *fphdr)
{
  int result = -1;

  // Set result data which doesn't depend on operation result.
  replyframe->service_id = GIMNETAP_SERVICE_NAMESERVICE_ID;
  replyframe->flags = KAPNameServiceFlagReply;
  replyframe->request = KAPNameServiceRequestIdByName;
  replyframe->identifier = nsframe->identifier;
  memcpy(replyframe->name, nsframe->name, sizeof(nsframe->name)); // AS-IS.
  
  // Perform query quality checks; if OK, continue inside if().
  if (isValidName(nsframe->name, sizeof(nsframe->name))) {
                                                                
    // Now, search entry from nameTable.
    const TNameTableEntry *entry = nametableGetEntryByName(nsframe->name);

    // Found?
    if (entry) {
      // FOUND, Do validity checks
      if (updateValidity((TNameTableEntry *)entry) == 0) {
        // Still OK.
        replyframe->nodeid = entry->nodeid;
        replyframe->age_left = (uint32_t)nametableEntryGetTimeToExpiration(entry);
        replyframe->result = KAPNameServiceResultOK;
        result = 0;
        
      } else {
        // Entry found, but expired. Marking as failure and returning
        // "NotFound". No reason to separate expired from notfound.
        replyframe->nodeid = 0x00000000;
        replyframe->age_left = 0;
        replyframe->result = KAPNameServiceResultEntryNotFound;

      }

    } else {
      // Not found! Set result code and data accordingly.
      replyframe->nodeid = 0x00000000;
      replyframe->age_left = 0;
      replyframe->result = KAPNameServiceResultEntryNotFound;

    }
  } else {
    replyframe->nodeid = 0x00000000;
    replyframe->age_left = 0;
    replyframe->result = KAPNameServiceResultInvalidName;
    
  }
  
  return result;
}
//*****************************************************************************

int nameservicecoreRequestNameById(TAPNameServiceFrame *replyframe, 
                                   const TAPNameServiceFrame *nsframe, 
                                   const TFastProtocolHeader *fphdr)
{
  int result = -1;

  // Set result data which doesn't depend on operation result.
  replyframe->service_id = GIMNETAP_SERVICE_NAMESERVICE_ID;
  replyframe->flags = KAPNameServiceFlagReply;
  replyframe->request = KAPNameServiceRequestNameById;
  replyframe->identifier = nsframe->identifier;
  replyframe->nodeid = nsframe->nodeid;
  
  // Now, search entry from nameTable.
  const TNameTableEntry *entry = nametableGetEntryByNodeId(nsframe->nodeid);
  
  // Found?
  if (entry) {
    // FOUND, Do validity checks
    if (updateValidity((TNameTableEntry *)entry) == 0) {
      // Still OK.
      memcpy(replyframe->name, entry->name, sizeof(nsframe->name)); // AS-IS.
      replyframe->age_left = (uint32_t)nametableEntryGetTimeToExpiration(entry);
      replyframe->result = KAPNameServiceResultOK;
      result = 0;
      
    } else {
      // Entry found, but expired. Marking as failure and returning
      // "NotFound". No reason to separate expired from notfound.
      memset(replyframe->name, 0x00, sizeof(replyframe->name));
      replyframe->age_left = 0;
      replyframe->result = KAPNameServiceResultEntryNotFound;
      
    }
    
  } else {
    // Not found! Set result code and data accordingly.
    memset(replyframe->name, 0x00, sizeof(replyframe->name));
    replyframe->age_left = 0;
    replyframe->result = KAPNameServiceResultEntryNotFound;
    
  }
  
  return result;
}
//*****************************************************************************

int nameservicecoreRequestRegister(TAPNameServiceFrame *replyframe, 
                                   const TAPNameServiceFrame *nsframe, 
                                   const TFastProtocolHeader *fphdr)
{
  int result = -1;

  // Set result data which doesn't depend on operation result.
  replyframe->service_id = GIMNETAP_SERVICE_NAMESERVICE_ID;
  replyframe->flags = KAPNameServiceFlagReply;
  replyframe->request = KAPNameServiceRequestRegister;
  replyframe->identifier = nsframe->identifier;
  memcpy(replyframe->name, nsframe->name, sizeof(nsframe->name)); // AS-IS.
  
  // Perform query quality checks; if OK, continue inside if().
  if (isValidName(nsframe->name, sizeof(nsframe->name))) {
    uint32_t validity = 0;

    // Check whether the validity is within given limits, and override
    // if not (no error)
    if (nsframe->age_left > nameservicecore.name_cache_validity_s_max) {
      validity = nameservicecore.name_cache_validity_s_max;
    } else if (nsframe->age_left < nameservicecore.name_cache_validity_s_min) {
      validity = nameservicecore.name_cache_validity_s_min;
    } else {
      validity = nsframe->age_left;
    }

    // More data not dependant on final result.
    replyframe->nodeid = fphdr->source_id;
    replyframe->age_left = validity;
    const TNameTableEntry *oldentry = NULL;

    // Attempt to carry out registration on database (conditional operation)
    result = nametableInsertEntry(nsframe->name, fphdr->source_id, validity, &oldentry);

    // Check result and act upon
    if (result == KNametableResultOK) {
      // OK. Operation completed without special events.
      replyframe->result = KAPNameServiceResultOK;
      
    } else if (result == KNametableResultConflict) {
      // Operation resulted in conflict. So either name or id was already registered.
      // At this point, oldentry must be non-NULL.
      assert(oldentry != NULL);
      
      // Check which value matches. (name_match must be calculated in all cases)
      const int name_match = strcmp(oldentry->name, replyframe->name) == 0;
      
      // Assert
      assert(name_match || oldentry->nodeid == replyframe->nodeid);
      
      // Act. (Strange arrangement, which optimizes the number of comparisons made)
      if (name_match) {
        // Name matches
        if (oldentry->nodeid == replyframe->nodeid) {
          // .. and id matches. This is an update.
          // Typecast to non-const.
          TNameTableEntry *entry = (TNameTableEntry*)oldentry;
          replyframe->result = KAPNameServiceResultOK;
          nametableUpdateTimestamp(entry);
          result = 0;
          
        } else {
          // Only name matches -> This is a real conflict.
          replyframe->result = KAPNameServiceResultNameReserved;
          result = -1;
        }
        
      } else {
        // Only Id matches -> This is a real conflict
        replyframe->result = KAPNameServiceResultNodeIdReserved;
        result = -1;
      }
      
      // Now, check whether the entry we got resulted in failure after
      // all checks
      if (result == -1) {
        if (updateValidity((TNameTableEntry *)oldentry) != 0) {
          // So, the entry existed, but was determined to be expired!
          // So, update will have removed it and we will carry on with
          // unconditional insert.
          result = nametableInsertEntry(nsframe->name, fphdr->source_id, validity, NULL);
          if (result == KNametableResultOK) replyframe->result = KAPNameServiceResultOK;
          
        }
      }
      
      // Now, finally; if result is still error; Check whether OVERRIDE was requested.
      if (result == -1 && nsframe->flags & KAPNameServiceFlagOverride) {
        dPrintLCRed(ODTEST,"Name Registration override requested! (Overriding name '%s', currently bound to '%08x')", 
                    nsframe->name, oldentry->nodeid);
        
        // Conflict + Override present -> REGISTER!
        int dr = nametableDeleteEntryByEntryPtr((TNameTableEntry *)oldentry);
        assert(dr == 0);
        result = nametableInsertEntry(nsframe->name, fphdr->source_id, validity, NULL);
        if (result == KNametableResultOK) replyframe->result = KAPNameServiceResultOK;
        
      }
      
    } else {
      replyframe->result = KAPNameServiceResultFailedUnknown;
      result = -1;
      
    }
    
  } else {
    // Not found! Set result code and data accordingly.
    replyframe->nodeid = 0x00000000;
    replyframe->age_left = 0;
    replyframe->result = KAPNameServiceResultInvalidName;
    
  }
  
  return result;
}
//*****************************************************************************

int nameservicecoreRequestUnregister(TAPNameServiceFrame *replyframe, 
                                     const TAPNameServiceFrame *nsframe, 
                                     const TFastProtocolHeader *fphdr)
{
  int result = -1;

  // Set result data which doesn't depend on operation result.
  replyframe->service_id = GIMNETAP_SERVICE_NAMESERVICE_ID;
  replyframe->flags = KAPNameServiceFlagReply;
  replyframe->request = KAPNameServiceRequestUnregister;
  replyframe->identifier = nsframe->identifier;
  memcpy(replyframe->name, nsframe->name, sizeof(nsframe->name)); // AS-IS.
  replyframe->nodeid = 0x00000000;
  replyframe->age_left = 0;
  
  // Perform query quality checks; if OK, continue inside if().
  if (nsframe->name[0] == '\0' ||
      isValidName(nsframe->name, sizeof(nsframe->name))) {
    const TNameTableEntry *entry = NULL;
    
    // IF; nodeid is set on incoming packet, delete by it, otherwise,
    // delete by name. (id is a lot faster)
    if (nsframe->nodeid) {
      entry = nametableGetEntryByNodeId(nsframe->nodeid);
      replyframe->nodeid = nsframe->nodeid;
    } else {
      entry = nametableGetEntryByName(nsframe->name);
    }
    
    // Found?
    if (entry) {
      // Yes - Delete! (Cast out constness)
      result = nametableDeleteEntryByEntryPtr((TNameTableEntry *)entry);
      assert(result == KNametableResultOK); // Entry was previously found, so this must succeed!
      replyframe->result = KAPNameServiceResultOK;
      
    } else {
      // No entry found.
      replyframe->result = KAPNameServiceResultEntryNotFound;
      
    }
    
  } else {
    // Not found! Set result code and data accordingly.
    replyframe->nodeid = 0x00000000;
    replyframe->age_left = 0;
    replyframe->result = KAPNameServiceResultInvalidName;
    
  }
  
  return result;
}
//*****************************************************************************

int nameservicecoreRequestUnknown(TAPNameServiceFrame *replyframe, 
                                  const TAPNameServiceFrame *nsframe, 
                                  const TFastProtocolHeader *fphdr)
{
  int result = -1;
  
  // Set result data which doesn't depend on operation result.
  replyframe->service_id = GIMNETAP_SERVICE_NAMESERVICE_ID;
  replyframe->flags = KAPNameServiceFlagReply;
  replyframe->request = nsframe->request;
  replyframe->identifier = nsframe->identifier;
  memcpy(replyframe->name, nsframe->name, sizeof(nsframe->name)); // AS-IS.
  replyframe->nodeid = 0x00000000;
  replyframe->age_left = 0;
  replyframe->result = KAPNameServiceResultFailedUnknown;
  
  return result;
}
//*****************************************************************************
//*****************************************************************************
